/**
 *******************************************************************************************************************************************
 * @file        userboot.c
 * @brief       IAP 实现 UserBoot 功能
 * @details     必看说明 :
 * 由于 UserBoot 属于一个功能模块, 需要结合 APP 使用, 且 UserBoot 并不是所有人都需要用, 故此处没有帮用户设置成 <开箱即用> 的方式.
 * 针对本工程内所有目标(除 UserBoot 自身) => 要使用 UserBoot ,
 * 必须在 Keil 下修改 每个 APP 固件 的 下载地址 与 大小, 将固件 下载起始地址 设为 [0x10000], 大小 设为 [0x70000].
 *
 * 修改方法须根据 在 Keil 工程的 Option -> Linker 的 [User Memory Layout from Target Dialog] 选项是否被<勾选> 分为如下情况:
 *
 * 一、[未勾选 X ] : 在 Keil 工程的 Option -> Linker 的 Scatter File 项点击 Edit... , 修改 [LR_IROM1] 与 [ER_IROM1] 的 <Start> <Size> 值
 *
 * 二、[已勾选 √ ] : 在 Keil 工程的 Option -> Target -> Read Only Memory Areas [on-chip] 中设置 <已被勾选的> Start 与 Size 值
 *
 * 如无特殊情况, 本例程已将所有目标(除 UserBoot 自身)设为上述第 一 种情形, 两种情形互斥, 一方有效则另一方无效.
 *
 * 而对于 UserBoot 目标, 笔者已将各项值设定好/无须用户修改, 此外, 上述所有值均依赖于本文件的 <UserBoot_Size = 0x10000> 计算得出.
 * @since       Change Logs:
 * Date         Author       Notes
 * 2022-12-08   lzh          the first version
 * @remark      UserBoot 编写说明 :  Device Firmware Update (OTA须借助外部无线模块)
 *
 * 加载方式 : 待更新的固件以文件形式存储于用户外部存储介质中, 待用户插入外部存储介质,
 * 将待更新的固件拷贝至 SPI-Flash 本地离线存储, 便于当用户选择合适的时机再(重启)执行更新动作 拷贝至 Chip-Flash 执行
 * 如此使得 固件下载拷贝 与 固件更新 的动作得到分隔, 当用户执行 固件更新 时, 如检测到 Chip-Flash 中已有固件,
 * 会将 旧固件 备份至 SPI-Flash 本地离线存储, 用于系统宕机时的 固件恢复, 若更新成功则会再次备份现有最新固件.
 *
 * 存储方式 : 双分区[备份区放在 SPI-Flash ] <若备份区放在 Chip-Flash 对 Chip-Flash 大小要求较高, 需要压缩 APP>,
 * 后台式下载新固件至 SPI-Flash , 在更新至 chip-Flash 时作读出 旧固件 备份至 SPI-Flash,
 * 若固件更新成功, 则保留在 SPI-Flash 本地离线存储 的待更新固件 会删除掉, 避免触发重复更新动作.
 *
 * 加密方式 : 固件(*.bin)头部加入固件信息头(带 CRC32 校验),
 * 利用每一颗芯片内的 UID 作 <密钥> 将存储在 SPI-Flash 本地的离线固件进行加密,
 * 除本机外其他机器无法使用, 但不利于 SPI-Flash 母片批量烧录方法
 *
 * (SWM341 没有硬件加密模块, 须软件实现加密算法: 对称 - AES, 非对称 - RSA/ECC, 哈希 - MD5<不可还原>)
 *
 * 须提示用户, 在更新时出现1~2次重启是正常现象, 例如 Windows 更新.
 *
 * 由于本例程适配各种分辨率的屏, 加之考虑到更新过程不会经常触发, 本着在 UB 中尽量精简 外设/功能 的设计原则, 故目前的 UserBoot 未实现屏显.
 * 更新过程中 可以增加不同颜色 LED 灯 快/慢 闪烁、蜂鸣器发声 等用户提示.
 *
 * @test        实际测试 UserBoot 更新 APP 固件时间约 30 s 以内, 备份 APP 同上.
 * @todo        增加线性地址写入
 *******************************************************************************************************************************************
 * @attention
 *
 * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS WITH CODING INFORMATION
 * REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE TIME. AS A RESULT, SYNWIT SHALL NOT BE HELD LIABLE
 * FOR ANY DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
 * OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION CONTAINED HEREIN IN CONN-
 * -ECTION WITH THEIR PRODUCTS.
 *
 * @copyright   2012 Synwit Technology
 *******************************************************************************************************************************************
 */
#include <string.h>
#include "userboot.h"

#include "bsp_init.h"

/*******************************************************************************************************************************************
 * Private Define
 *******************************************************************************************************************************************/
//#define UB_SECURE_AES_ENABLE /**< 是否在 UserBoot 中添加及使用 AES 加密算法(软件实现会带来运行时间损耗)<待调_暂不可用> */

#define UB_DEBUG_ENABLE /**< 模块调试日志输出使能宏 */

/** 模块调试日志打印宏 */
#ifdef UB_DEBUG_ENABLE
    #define UB_LOG(...)                 printf(__VA_ARGS__)
#else
    #define UB_LOG(...)
#endif

/*******************************************************************************************************************************************
 * Private Typedef
 *******************************************************************************************************************************************/
/** 固件信息<结构体总大小须保持 4 Word 对齐(因 FLASH_Write() 最少写入 4 个字)> */
typedef struct
{
    uint32_t version; /**< 固件 版本(版本均可约定以程序日期为例"2022.10.16 => 0x20221016") */
    uint32_t size;    /**< 固件 大小(不包括附加信息) */
    uint32_t crc;     /**< 固件 CRC32 校验和(使用 固件版本 + 固件大小 的结果作 CRC32 数据源) */
    uint32_t reserve; /**< 保留填充 */
} fw_info_t;

/** 设备信息<结构体总大小须保持 4 Word 对齐(因 FLASH_Write() 最少写入 4 个字)> */
typedef struct
{
    uint32_t boot;         /**< UserBoot 引导启动标志 */
    uint32_t boot_version; /**< UserBoot 版本(版本均可约定以程序日期为例"2022.10.16 => 0x20221016") */
    uint32_t reserve[2];   /**< 保留填充 */
    fw_info_t app;         /**< 固件信息 */
} dev_info_t;

/*******************************************************************************************************************************************
 * Private Constant
 *******************************************************************************************************************************************/
#define Flash_Alignment (4096)                                                 /**< Flash-IAP 操作须对齐的大小( 4K 对齐 ) */
static const uint32_t Chip_Flash_Max_Size = 0x80000;                           /**< SWM341 系列 Flash 容量上限 512K Bytes */
static const uint32_t UserBoot_Addr = 0x00000;                                 /**< UserBoot 加载地址 */
static const uint32_t UserBoot_Size = 0x10000;                                 /**< UserBoot 预设大小 <4K 对齐> (可预留多一些, 以便后续有更新 UserBoot 的需求, 暂未预留接口实现) */
static const uint32_t UserBoot_Version = 0x20221016;                           /**< UserBoot 程序版本 */
static const uint32_t Firmware_Addr = UserBoot_Addr + UserBoot_Size;           /**< APP 加载地址 */
static const uint32_t Firmware_Max_Size = Chip_Flash_Max_Size - Firmware_Addr; /**< APP 大小上限 */

/** 将设备信息存储于 ROM 中(可以是 Chip-Flash 或 SPI-Flash )<以 4K Bytes 地址对齐存储于 UserBoot 末尾 <-> APP 运行地址前>
 *  注意 : 该定向指针只读, 不允许写入, 写入须调用 set_device_info() 函数
 */
static const dev_info_t *const Device_Info = (const dev_info_t *const)(Firmware_Addr - Flash_Alignment);

/** 当第一次上电没有固件时, 默认去搜寻的外部磁盘类型(考虑到 SWM34SCE-48Pin 没有 SDIO 端口) */
static const disk_t Move_Disk = USB;

/*******************************************************************************************************************************************
 * Public Constant
 *******************************************************************************************************************************************/
/** 存放至本地 (SPI-Flash) 的待更新固件文件名 */
const char *const Firmware_Name_Update = "Firmware_SWM34S_Admin.bin";
/** 存放至本地 (SPI-Flash) 的备份固件文件名 */
const char *const Firmware_Name_Backup = "Firmware_SWM34S_Backups.bin";
/** 本地磁盘类型 */
const disk_t Firmware_Disk = SPI;

/*******************************************************************************************************************************************
 * Private Variable
 *******************************************************************************************************************************************/
#ifdef UB_SECURE_AES_ENABLE
// AES
//#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/aes.h"
/// 安全加密 AES
static mbedtls_aes_context AES;
/// AES 下的初始化向量 ( Intialization Vector ) <与明文分组对应的密文分组>
static uint8_t IV[16];
/// 向量默认初值
static uint8_t IV_Init[16] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
#endif

/** 读缓冲区(消耗RAM太大, 优化放置在 SDRAM ) */
static uint8_t Read_Buff[Flash_Alignment] __attribute__(( section(".SDRAM1"), aligned(4) ));
/** 写缓冲区(消耗RAM太大, 优化放置在 SDRAM ) */
static uint8_t Write_Buff[Flash_Alignment] __attribute__(( section(".SDRAM1"), aligned(4) ));

/*******************************************************************************************************************************************
 * Private Prototype
 *******************************************************************************************************************************************/
static void reset_mcu(void);
static void jump_to_app(const uint32_t addr);
static inline uint32_t calculate_crc(fw_info_t *data);
static uint8_t set_device_info(const dev_info_t *ptr);
static uint8_t firmware_backup(const char *path, data_code_cb data_enc);
static uint8_t firmware_update(const char *path, data_code_cb data_dec);

/*******************************************************************************************************************************************
 * Public Function
 *******************************************************************************************************************************************/
/**
 * @brief   UserBoot 功能
 * @param   /
 * @retval  /
 * @note    本函数会一直阻塞在被调用处, 之后的语句都不会得到执行
 */
void userboot(void)
{
    uint8_t res = 0;
    /* 已在 startup_SWM341.s 的 Reset_Handler 中调用
    SystemInit();
    sfc_init();   //初始化总线上外挂的SPI-Flash, 将其映射至MCU内部(可选外设)
    sdram_init(); //初始化总线上外挂的SDRAM<芯片内置>, 将其映射至MCU内部(可选外设)
    */
    if (SYS->RSTSR & SYS_RSTSR_POR_Msk) // 1 出现过 POR 复位, 写1清零
    {
        SYS->RSTSR |= (1 << SYS_RSTSR_POR_Pos);
    }
    if (SYS->RSTSR & SYS_RSTSR_WDT_Msk) // 1 出现过 WDT 复位, 写1清零
    {
        SYS->RSTSR |= (1 << SYS_RSTSR_WDT_Pos);
    }

    //检测启动引导标志位
    if (Device_Info->boot == GO_APP)
    {
        /* 跳转到APP前需要将 UserBoot 使用的外设关掉（复位）*/
        __disable_irq();
        
#if 0 /* 由于 Systick 属于内核附带的外设, 如在 Boot 中有使用开启, 则需要单独使用以下语句关闭, 否则跳转 APP 将会失败 */
        /* Disable SysTick IRQ and SysTick Timer */
        SysTick->CTRL &= ~(SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk);
#endif
        /* 个别外设不能重复初始化, 故需要在跳转前先复位,
         * 但对于 GPIO 而言, 由于复位后为浮空状态, 对于需要保持 IO 输出电平的场景(eg : IO控制外部器件供电), 跳转时无需复位 GPIO 
         */
        SYS->PRSTEN = 0x55;
        SYS->PRSTR0 = 0xFFFFFFFF & (~SYS_PRSTR0_ANAC_Msk); /* 始终不允许复位 ANAC 模拟控制单元 */
        SYS->PRSTR1 = 0xFFFFFFFF;
        for (int i = 0; i < CyclesPerUs; i++)
            __NOP();
        SYS->PRSTR0 = 0;
        SYS->PRSTR1 = 0;
        SYS->PRSTEN = 0;

        NVIC->ICER[0] = 0xFFFFFFFF;
        NVIC->ICER[1] = 0xFFFFFFFF;
        NVIC->ICER[2] = 0xFFFFFFFF;

        jump_to_app(Firmware_Addr);
    }

    uart_debug_init(); //仅用作调试日志打印

    crc_init(); //硬件 CRC32 初始化

    //芯片可以提供唯一 96 bit ID 号用于加密使用
    uint32_t chip_id[128 / 32];
    chip_id[0] = SYS->CHIPID[0];
    chip_id[1] = SYS->CHIPID[1];
    chip_id[2] = SYS->CHIPID[2];
    chip_id[3] = SYS->CHIPID[0]; //不足的按重复填充
    UB_LOG("The [Chip_ID] = 0x[%x][%x][%x]\r\n", chip_id[0], chip_id[1], chip_id[2]);

#ifdef UB_SECURE_AES_ENABLE
    //初始化 AES 算法, 并设置密钥
    mbedtls_aes_init(&AES);
    mbedtls_aes_setkey_enc(&AES, (const uint8_t *)chip_id, 128);
    mbedtls_aes_setkey_dec(&AES, (const uint8_t *)chip_id, 128);
    // mbedtls_aes_free(&AES);

    /* 使用硬件 PRNG 随机数 填充 IV 初值
    //设置密文分组的默认初值
    SYS->HRCCR |= (1 << SYS_HRCCR_ON_Pos);
    SYS->LRCCR |= (1 << SYS_LRCCR_ON_Pos);
    SYS->PRNGCR = (0 << SYS_PRNGCR_CLR_Pos) |
                  (3 << SYS_PRNGCR_MODE_Pos);
    for (uint32_t i = 0; i < sizeof(IV) / sizeof(IV[0]); ++i)
    {
        while ((SYS->PRNGCR & SYS_PRNGCR_RDY_Msk) == 0)
            __NOP();
        IV_Init[i] = SYS->PRNGDH + SYS->PRNGDL;
        // UB_LOG("PRNGDL: %08X, PRNGDH: %08X\n", SYS->PRNGDL, SYS->PRNGDH);

        for (i = 0; i < SystemCoreClock / 8; i++)
            __NOP();
    }
    */
    memcpy(IV, IV_Init, sizeof(IV)); //重置密文分组

#endif

    char firmware_path_update[FF_MAX_LFN]; //更新固件的绝对路径
    sprintf(firmware_path_update, "%s%s", VolumeStr[Firmware_Disk], Firmware_Name_Update);

    char firmware_path_backup[FF_MAX_LFN]; //备份固件的绝对路径
    sprintf(firmware_path_backup, "%s%s", VolumeStr[Firmware_Disk], Firmware_Name_Backup);

    char firmware_path_move[FF_MAX_LFN]; //外部固件的绝对路径
    sprintf(firmware_path_move, "%s%s", VolumeStr[Move_Disk], Firmware_Name_Update);

    FILINFO fno;

    res = fs_mount(Firmware_Disk);
    if (res != 0) //本地存储挂载文件系统
    {
        UB_LOG("The [fs_mount] <Firmware_Disk[%d]> result is [%d]!\r\n", Firmware_Disk, res);

        //若出现此种情况, 此处应执行一些特定严重警告的语句以提示用户
        //while (1) __NOP();//不允许出现本地存储无法挂载的情况, 这会导致 UserBoot 功能无法执行
        goto exit_reset;
    }

    switch (Device_Info->boot)
    {
    /*
    case (GO_APP): //跳转至 app 运行
        jump_to_app(Firmware_Addr);
        break;
    */
    case (EXEC_UPDATE): //检测到有可更新的固件, 进行更新

        if (FR_OK != f_stat(firmware_path_backup, &fno)) //如果没有备份固件, 则先进行备份
        {
            res = firmware_backup(firmware_path_backup, data_encode);
        }
        if (res == 0) //第一份固件备份成功
        {
            res = firmware_update(firmware_path_update, data_decode);
            if (res == 0) //更新完成, 可以跳转
            {
                res = firmware_backup(firmware_path_backup, data_encode);
                UB_LOG("The [firmware_backup] backup[%s], result is [%d]!\r\n", firmware_path_backup, res);
                jump_to_userboot(GO_APP, true);
            }
        }
        //上述工作流程出现错误
        UB_LOG("The [EXEC_UPDATE] backup[%s] update[%s], result is [%d]!\r\n", firmware_path_backup, firmware_path_update, res);
        break;

    case (BACKUP_RESTORE): //出现错误变砖后, 从备份区恢复固件
        res = firmware_update(firmware_path_backup, data_decode);
        if (res == 0) //写入旧固件完成, 可以跳转恢复
        {
            jump_to_userboot(GO_APP, true);
        }
        //上述工作流程出现错误
        UB_LOG("The [BACKUP_RESTORE] backup[%s], result is [%d]!\r\n", firmware_path_backup, res);
        break;

    default: //这是 UserBoot 第一次启动, 没有经过 APP 更新函数 故必然没有固件头, 此时应检测是否存在通过 Keil 下载的原生固件

        /*  通过判断栈定地址值是否在 0x2000 0000 - 0x2001 0000 之间 ( SWM341 RAM Max : 0x1 0000 <64KB> ) 来判断应用程序是否已经下载到指定地址.
         *  因为中断向量表里第一个放的就是栈顶地址的值, 如果该值不在 RAM 区间地址内, 即使这是固件也是不合法的.
         */
        if (((*(volatile uint32_t *)(Firmware_Addr)) & 0x2FFE0000) == 0x20000000 &&
            ((*(volatile uint32_t *)(Firmware_Addr)) & 0x0001FFFF) < 0x00010000)
        {
            //原生固件已经存在, 可以直接运行 app (此处默认原生固件可正常执行无误, 该项由用户保证, 因为这是用户通过 Keil 下载的, 理应如此)
            UB_LOG("APP Already Exist!\r\n");

            dev_info_t dev_info;
            dev_info.boot = GO_APP;
            //配置写入默认初始化固件信息
            dev_info.boot_version = UserBoot_Version;
            dev_info.reserve[0] = 0;
            dev_info.reserve[1] = 0;
            // APP 默认版本跟随 UserBoot 版本, 因通过keil下载无法获取app大小, 故默认大小为除 UserBoot 占用之外的全部剩余 Chip-Flash 大小
            dev_info.app.version = UserBoot_Version;
            dev_info.app.size = Firmware_Max_Size;
            dev_info.app.crc = calculate_crc(&(dev_info.app));
            dev_info.app.reserve = 0;
            //写入设备配置信息
            res = set_device_info(&dev_info);
            if (res != 0)
            {
                UB_LOG("The [set_device_info] result is [%d]!\r\n", res);
            }
            goto exit_reset;
        }
        else
        {
            //没有原生固件, 可能是用户下载 userboot 时遗漏忘了 app ,
            //分别去 [ 外部存储(SD) 、本地存储(SPI-Flash) ] 寻找是否有可用固件, (外部存储权重更高)
            //若搜寻无果-没有可用固件, 需要提示用户升级/告警, 并停留在 UserBoot 中不断复位
            uint32_t i = 0;
            do
            {
                res = fs_mount(Move_Disk);
                for (uint32_t j = 0; j < CyclesPerUs; ++j)
                    __NOP();
                ++i;
            } while (i < CyclesPerUs * 1000 && res != 0); //检测一段时间

            if (res == 0) //外部存储挂载文件系统
            {
                res = firmware_download(firmware_path_move, firmware_path_update, data_encode);
                if (res == 0) //外部存储中有 可用更新固件, 成功下载至本地
                {
                    res = firmware_update(firmware_path_update, data_decode);
                    if (res == 0) //更新完成, 可以跳转
                    {
                        //由于是第一次下载的固件, 故将 新固件 备份
                        res = firmware_backup(firmware_path_backup, data_encode);
                        UB_LOG("The [firmware_backup] backup[%s], result is [%d]!\r\n", firmware_path_backup, res);

                        jump_to_userboot(GO_APP, true);
                    }
                }
                fs_unmount(Move_Disk); //外部存储卸载文件系统
            }
            else //未插入外部存储
            {
                UB_LOG("The [fs_mount] <Firmware_Disk[%d]> result is [%d]!\r\n", Move_Disk, res);

                res = firmware_update(firmware_path_update, data_decode);
                if (res == 0) //更新完成, 可以跳转
                {
                    //由于是第一次下载的固件, 故将 新固件 备份
                    res = firmware_backup(firmware_path_backup, data_encode);
                    UB_LOG("The [firmware_backup] backup[%s], result is [%d]!\r\n", firmware_path_backup, res);

                    jump_to_userboot(GO_APP, true);
                }
                else
                {
                    res = firmware_update(firmware_path_backup, data_decode);
                    if (res == 0) //更新完成, 可以跳转
                    {
                        UB_LOG("The [firmware_update] backup[%s], result is [%d]!\r\n", firmware_path_backup, res);
                        jump_to_userboot(GO_APP, true);
                    }
                }
            }
        }
        break;
    }
exit_reset:
    fs_unmount(Firmware_Disk); //本地存储卸载文件系统
    reset_mcu();

    for (;;)
    {
        // Add Your Code
    }
    // No Exec Here <return>
}

/**
 * @brief   跳转至 UserBoot 执行对应动作
 * @param   boot_flag : UserBoot 引导启动标志(详见对应枚举)
 * @param   wait      : 是否立即跳转 (true-立即复位跳转      false-等待用户手动复位后跳转)
 * @retval  /
 * @note    此函数会改变 Chip-Flash 内部分区域的数据, 若片内不含 UB 程序, 则调用本程序会导致 APP 的正常数据被破坏.
 */
void jump_to_userboot(uint32_t boot_flag, bool wait)
{
    uint8_t res = 0;
    dev_info_t dev_info;
    dev_info.app = Device_Info->app;
    dev_info.boot_version = Device_Info->boot_version;
    dev_info.reserve[0] = Device_Info->reserve[0];
    dev_info.reserve[1] = Device_Info->reserve[1];

    switch (boot_flag)
    {
    case (GO_APP):         // continue
    case (EXEC_UPDATE):    // continue
    case (BACKUP_RESTORE): // continue
        dev_info.boot = boot_flag;
        break;

    default: //传入其他的错误状态数据, 不生效
        dev_info.boot = Device_Info->boot;
        UB_LOG("[jump_to_userboot] is invalid! [boot_flag] = 0x[%x], [dev_info.boot] = 0x[%x]\r\n", boot_flag, dev_info.boot);
        break;
    }
    //写入设备配置信息
    res = set_device_info(&dev_info);
    if (res != 0)
    {
        UB_LOG("The [set_device_info] result is [%d]!\r\n", res);
    }

    if (wait)
    {
        reset_mcu();
    }
}

/**
 * @brief   固件下载 (外部 => 本地)
 * @param   src_path : 外部固件绝对路径
 * @param   dst_path : 本地固件绝对路径
 * @param   data_enc : 数据加密回调
 * @retval  0        : success
 * @retval  other    : error code
 * @pre     须保证 源/目标 固件对应磁盘的文件系统处于挂载状态
 */
uint8_t firmware_download(const char *src_path, const char *dst_path, data_code_cb data_enc)
{
    //读写缓冲区
    //uint8_t Read_Buff[Flash_Alignment] __attribute__((aligned(4)));
    //uint8_t Write_Buff[Flash_Alignment] __attribute__((aligned(4))); //消耗RAM太大, 易爆栈

    uint8_t res = 0;                      //返回值
    FIL src_file, dst_file;               //文件指针
    uint32_t read_len = 0, write_len = 0; //读/写文件字节数
    uint32_t firmware_size = 0;           //固件大小 / Bytes

    //  FA_CREATE_ALWAYS : 创建一个新文件，如果文件已存在，则它将被截断并覆盖
    //  FA_OPEN_ALWAYS   : 如果文件存在，则打开；否则，创建一个新文件
    //  FA_OPEN_EXISTING : 打开一个文件。如果文件不存在，该函数将失败
    res = f_open(&src_file, src_path, FA_READ | FA_OPEN_EXISTING);
    if (FR_OK != res)
    {
        UB_LOG("[%s] file [f_open] = [%d]\r\n", src_path, res);
        return res;
    }
    res = f_open(&dst_file, dst_path, FA_WRITE | FA_OPEN_ALWAYS);
    if (FR_OK != res)
    {
        f_close(&src_file);
        UB_LOG("[%s] file [f_open] = [%d]\r\n", dst_path, res);
        return res;
    }
    //===============================================校验固件===============================================//
    //文件光标定位
    f_lseek(&src_file, 0);
    //获取固件大小
    firmware_size = f_size(&src_file);
    //获取固件信息
    fw_info_t firmware_info;
    res = f_read(&src_file, &firmware_info, sizeof(fw_info_t), &read_len);
    if (res != FR_OK || read_len < sizeof(fw_info_t))
    {
        if (read_len < sizeof(fw_info_t))
            res = 1;

        UB_LOG("The Firmware don't get info, result = [%d]!\r\n", res);
        goto firmware_download_over;
    }
    firmware_size -= read_len; //得出实际的固件大小
    UB_LOG("The [firmware_download] Version = 0x[%x], Size = 0x[%x], CRC = 0x[%x]\r\n", firmware_info.version, firmware_info.size, firmware_info.crc);

    //判断固件是否有效 / 固件信息是否准确
    if (firmware_info.size > Firmware_Max_Size ||
        firmware_info.size != firmware_size ||
        calculate_crc(&firmware_info) != firmware_info.crc)
    {
        res = 2;
        UB_LOG("The Firmware is invalid! calculate_crc = 0x[%x]\r\n", calculate_crc(&firmware_info));
        goto firmware_download_over;
    }
    // f_lseek(&src_file, 0);//不加密 固件信息头 (直接跳过), 对 备份/更新 同理

    //===============================================校验通过===============================================//
#ifdef UB_SECURE_AES_ENABLE
    memcpy(IV, IV_Init, sizeof(IV)); //重置密文分组
#endif
    //===============================================下载固件===============================================//
    f_lseek(&dst_file, 0);
    res = f_write(&dst_file, &firmware_info, read_len, &write_len);
    if (write_len < read_len) //写入不完整,大概率是目录已满
    {
        res = FR_DENIED;
        UB_LOG("[%s] file [read_len] = [%d], [write_len] = [%d], result = [%d]\r\n", dst_path, read_len, write_len, res);
    }
    for (uint32_t offset = 0; FR_OK == res && offset < firmware_size; offset += write_len) //文件拷贝开始
    {
        res = f_read(&src_file, Read_Buff, sizeof(Read_Buff), &read_len);
        if (FR_OK != res || read_len == 0) //读取文件失败或者已读到文件结尾
        {
            // UB_LOG("[%s] last read_len = [%d] \r\n possible reason: [%d] file read over!\r\n", src_path, read_len, res);
            break;
        }

        //最后不足 16 Bytes 的部分进行 zero padding (因为 Flash_write 单次操作 Unit 是四个字, 无论用不用加密算法都要对齐)
        for (; (read_len & 0xF) != 0; ++read_len)
            Read_Buff[read_len] = 0;

        if (data_enc != NULL) //加密
        {
            if (data_enc(Read_Buff, Write_Buff, read_len) != 0)
            {
                res = 3;
                UB_LOG("The Firmware [data_enc] result = [%d]!\r\n", res);
                break;
            }
        }
        else
        {
            memcpy(Write_Buff, Read_Buff, read_len);
        }

        res = f_write(&dst_file, Write_Buff, read_len, &write_len);
        if (write_len < read_len) //最后一次写入不完整,大概率是目录已满
        {
            UB_LOG("[%s] file last [read_len] = [%d], [write_len] = [%d], result = [%d]\r\n", dst_path, read_len, write_len, res);
            res = FR_DENIED;
            break;
        }

        UB_LOG("=============== [Firmware] DownLoad [%d]/[%d] Bytes ===============\r\n", offset + write_len, firmware_size);
    }
    //===============================================下载结束===============================================//
firmware_download_over:
    f_close(&src_file);
    f_close(&dst_file);
    if (0 != res) //出现错误则删除目标文件, 必须先关闭文件, 否则 f_unlink 将被拒绝
    {
        /*res = */ f_unlink(dst_path);
    }
    return res;
}

/**
 * @brief   数据加密
 * @param   src_buff : 数据源
 * @param   dst_buff : 输出流
 * @param   bytes    : 数据量( AES 分组长度 128 bit == 16 Bytes, 数据量必须被该长度整除)
 * @retval  0        : success
 * @retval  other    : error code
 * @note    可使用 芯片唯一 96 bit ID 实现加密算法: 对称 - AES, 非对称 - RSA/ECC, 哈希 - MD5<不可还原>
 */
int data_encode(const void *src_buff, void *dst_buff, uint32_t bytes)
{
#ifdef UB_SECURE_AES_ENABLE
    return mbedtls_aes_crypt_cbc(&AES, MBEDTLS_AES_ENCRYPT, bytes, IV, src_buff, dst_buff);
#else
    memcpy(dst_buff, src_buff, bytes);
    return 0;
#endif
}

/**
 * @brief   数据解密
 * @param   src_buff : 数据源
 * @param   dst_buff : 输出流
 * @param   bytes    : 数据量( AES 分组长度 128 bit == 16 Bytes, 数据量必须被该长度整除)
 * @retval  0        : success
 * @retval  other    : error code
 * @note    依据 数据加密规则 data_encode() 逆向进行解码
 */
int data_decode(const void *src_buff, void *dst_buff, uint32_t bytes)
{
#ifdef UB_SECURE_AES_ENABLE
    return mbedtls_aes_crypt_cbc(&AES, MBEDTLS_AES_DECRYPT, bytes, IV, src_buff, dst_buff);
#else
    memcpy(dst_buff, src_buff, bytes);
    return 0;
#endif
}

/*******************************************************************************************************************************************
 * Private Function
 *******************************************************************************************************************************************/
/**
 * @brief   重启 MCU
 * @param   /
 * @retval  /
 * @note    相比于 NVIC_SystemReset() , 看门狗从硬件层面保证可靠性, 可确保寄存器的复位值均恢复默认.
 */
static void reset_mcu(void)
{
    WDT_Init(WDT, 0, 1);
    WDT_Start(WDT);
    for (;;)
        ;
}

/**
 * @brief   跳转至 APP 加载地址运行
 * @param   addr : APP 加载地址
 * @retval  /
 * @note    通过判断栈定地址值来辨别传入地址上的应用程序是否有效
 */
static void jump_to_app(const uint32_t addr)
{
    /* 启动先跳转到系统 BootLoader, 首地址是 MSP, 地址 + 4 是复位中断服务程序地址 */
    uint32_t sp = *((volatile uint32_t *)(addr));
    uint32_t pc = *((volatile uint32_t *)(addr + 4));

    typedef void (*Func_void_void)(void);
    Func_void_void ResetHandler = (Func_void_void)(0x00);

    /*  通过判断栈定地址值是否在 0x2000 0000 - 0x2001 0000 之间 ( SWM341 RAM Max : 0x10000 <64KB> ) 来判断应用程序是否已经下载到指定地址.
     *  因为中断向量表里第一个放的就是栈顶地址的值, 如果该值不在 RAM 区间地址内, 即使这是固件也是不合法的.
     */
    if ((sp & 0x2FFE0000) == 0x20000000 && (sp & 0x0001FFFF) < 0x00010000)
    {
        ResetHandler = (Func_void_void)pc;

        SCB->VTOR = addr; /* 设置中断向量表地址 */

        __set_MSP(sp); /* 设置主堆栈指针 */

        //__set_CONTROL(0); /* 注意使用 RTOS 时，设置为特权级模式，使用 MSP, 而不是 PSP */
    }
    else /* 该地址上没有可正常运行的固件, 传参异常抛出 */
    {
        // UB_LOG("No firmware for APP load address 0x[%x] \r\n", addr);
    }

    ResetHandler();

    for (;;)
    {
    }
}

/**
 * @brief   计算 CRC 校验和
 * @param   data : APP 固件信息
 * @retval  crc  : 校验和
 * @pre     依赖 CRC 硬件, 须在本函数调用前完成 CRC 外设的初始化.
 */
static inline uint32_t calculate_crc(fw_info_t *data)
{
    return get_crc32((data->version) + (data->size));
}

/**
 * @brief   写入固件信息, 并自带回读校验
 * @param   ptr   : 设备信息结构体指针
 * @retval  0     : success
 * @retval  other : error code
 */
static uint8_t set_device_info(const dev_info_t *ptr)
{
    uint8_t res = 0;
    res = FLASH_Erase((uint32_t)Device_Info);
    if (res != FLASH_RES_OK)
    {
        return res;
    }
    
    res = FLASH_Write((uint32_t)Device_Info, (uint32_t *)ptr, sizeof(dev_info_t) >> 2);
    if (res != FLASH_RES_OK)
    {
        return res;
    }
    
    //回读校验
    if (Device_Info->app.version != ptr->app.version ||
        Device_Info->app.size != ptr->app.size ||
        Device_Info->app.crc != ptr->app.crc ||
        Device_Info->boot != ptr->boot ||
        Device_Info->boot_version != ptr->boot_version
        /*                                              ||
        Device_Info->app.reserve != ptr->app.reserve    ||
        Device_Info->reserve[0] != ptr->reserve[0]      ||
        Device_Info->reserve[1] != ptr->reserve[1]
        没用的信息就懒得校验了 */
        )
    {
        //#define FLASH_RES_OK  0
        //#define FLASH_RES_TO  1   //Timeout
        //#define FLASH_RES_ERR 2
        return 3;
    }
    /*
    if (res != 0)
    {
        while (1) __NOP();//不允许出现 Chip-Flash 写入失败的情况, 若出现了请排查用户层传入参数错误(数据源/地址/数据量对齐)
    }*/
    return res;
}

/**
 * @brief   固件备份 (Chip-Flash<运行区> => 本地存储<备份区>)
 * @param   path     : 待备份固件的绝对路径
 * @param   data_enc : 数据加密回调
 * @retval  0        : success
 * @retval  other    : error code
 * @pre     须保证 目标固件 对应磁盘的文件系统处于挂载状态
 */
static uint8_t firmware_backup(const char *path, data_code_cb data_enc)
{
    //读写缓冲区
    //uint8_t Write_Buff[Flash_Alignment] __attribute__((aligned(4)));//消耗RAM太大, 易爆栈

    FIL file;                             //文件指针
    uint8_t res = 0;                      //返回值
    uint32_t read_len = 0, write_len = 0; //读/写文件字节数
    uint32_t offset = 0;                  // Chip-Flash 读的偏移量
    uint32_t firmware_size = 0;           //固件大小 / Bytes

    //===============================================校验固件===============================================//
    //获取固件大小
    firmware_size = Device_Info->app.size;
    //获取固件信息
    fw_info_t firmware_info = Device_Info->app;
    UB_LOG("The [firmware_backup] Version = 0x[%x], Size = 0x[%x], CRC = 0x[%x]\r\n", firmware_info.version, firmware_info.size, firmware_info.crc);
    //判断固件是否有效 / 固件信息是否准确
    if (
        ((*(volatile uint32_t *)(Firmware_Addr)) & 0x2FFE0000) != 0x20000000 ||
        ((*(volatile uint32_t *)(Firmware_Addr)) & 0x0001FFFF) >= 0x00010000 ||
        firmware_size == 0 ||
        firmware_size > Firmware_Max_Size ||
        calculate_crc(&firmware_info) != firmware_info.crc)
    {
        res = 1;
        UB_LOG("The Firmware is invalid! calculate_crc = 0x[%x]\r\n", calculate_crc(&firmware_info));
        return res;
    }
    //===============================================校验通过===============================================//
    //  FA_CREATE_ALWAYS : 创建一个新文件，如果文件已存在，则它将被截断并覆盖
    //  FA_OPEN_ALWAYS   : 如果文件存在，则打开；否则，创建一个新文件
    //  FA_OPEN_EXISTING : 打开一个文件。如果文件不存在，该函数将失败
    res = f_open(&file, path, FA_WRITE | FA_OPEN_ALWAYS);
    if (FR_OK != res)
    {
        UB_LOG("The Firmware is [%s] file [f_open] = [%d]\r\n", path, res);
        return res;
    }
    f_lseek(&file, 0);
    //===============================================固件备份===============================================//
    //先写入 固件信息头 再写 实际固件
    res = f_write(&file, (void *)&firmware_info, sizeof(firmware_info), &write_len);
    if (FR_OK != res || write_len < sizeof(firmware_info)) //写入不完整-大概率是目录已满
    {
        UB_LOG("[%s] file [write_len] = [%d], result = [%d]\r\n", path, write_len, res);
        res = FR_DENIED;
        goto firmware_backup_over;
    }
#ifdef UB_SECURE_AES_ENABLE
    memcpy(IV, IV_Init, sizeof(IV)); //重置密文分组
#endif
    read_len = sizeof(Write_Buff); //按 4K 读写
    for (uint32_t offset = 0; FR_OK == res && offset < firmware_size; offset += read_len)
    {
        //最后一次 Write_Buff 不满
        if (firmware_size - offset < read_len)
        {
            read_len = firmware_size - offset;
            if ((read_len & 0xF) != 0) //最后不足 16 Bytes 的部分进行 padding
            {
                read_len = (read_len & (~0xF)) + 16;
            }
        }

        if (data_enc != NULL) //加密
        {
            if (data_enc((uint8_t *)(Firmware_Addr + offset), Write_Buff, read_len) != 0)
            {
                res = 2;
                UB_LOG("The Firmware [data_enc] result = [%d]!\r\n", res);
                break;
            }
        }
        else
        {
            memcpy(Write_Buff, (uint8_t *)(Firmware_Addr + offset), read_len);
        }

        res = f_write(&file, Write_Buff, read_len, &write_len);
        if (write_len < read_len) //写入不完整-大概率是目录已满
        {
            UB_LOG("[%s] file [write_len] = [%d], result = [%d]\r\n", path, write_len, res);
            res = FR_DENIED;
            break;
        }
        UB_LOG("=============== [Firmware] Backup [%d]/[%d] Bytes ===============\r\n", offset + read_len, firmware_size);
    }
    //===============================================结束备份===============================================//
firmware_backup_over:
    f_close(&file);
    return res;
}

/**
 * @brief   固件更新 (本地存储<备份区> => Chip-Flash<运行区>)
 * @param   path     : 待更新固件的绝对路径
 * @param   data_dec : 数据解密回调
 * @retval  0        : success
 * @retval  other    : error code
 * @pre     须保证 源固件 对应磁盘的文件系统处于挂载状态
 */
static uint8_t firmware_update(const char *path, data_code_cb data_dec)
{
    //读写缓冲区
    //uint8_t Read_Buff[Flash_Alignment] __attribute__((aligned(4)));
    //uint8_t Write_Buff[Flash_Alignment] __attribute__((aligned(4))); //消耗RAM太大, 易爆栈

    FIL file;                   //文件指针
    uint8_t res = 0;            //返回值
    uint32_t read_len = 0;      //读文件字节数
    uint32_t offset = 0;        // Chip-Flash 擦/写的偏移量
    uint32_t firmware_size = 0; //固件大小 / Bytes

    //  FA_CREATE_ALWAYS : 创建一个新文件，如果文件已存在，则它将被截断并覆盖
    //  FA_OPEN_ALWAYS   : 如果文件存在，则打开；否则，创建一个新文件
    //  FA_OPEN_EXISTING : 打开一个文件。如果文件不存在，该函数将失败
    res = f_open(&file, path, FA_READ | FA_OPEN_EXISTING);
    if (FR_OK != res)
    {
        UB_LOG("The Firmware is [%s] file [f_open] = [%d]\r\n", path, res);
        return res;
    }
    //===============================================校验固件===============================================//
    //文件光标定位
    f_lseek(&file, 0);
    //获取固件大小
    firmware_size = f_size(&file);
    //获取固件信息
    fw_info_t firmware_info;
    res = f_read(&file, &firmware_info, sizeof(fw_info_t), &read_len);
    if (res != FR_OK || read_len < sizeof(fw_info_t))
    {
        if (read_len < sizeof(fw_info_t))
        {
            res = 1;
        }
        UB_LOG("The Firmware don't get info, result = [%d]!\r\n", res);
        goto firmware_update_over;
    }
    firmware_size -= read_len; //得出实际的固件大小(经过 16 字节对齐填充后有可能与 firmware_info.size 不一致)
    UB_LOG("The [firmware_update] Version = 0x[%x], Size = 0x[%x], CRC = 0x[%x]\r\n", firmware_info.version, firmware_info.size, firmware_info.crc);

    //判断固件是否有效 / 固件信息是否准确
    if ((firmware_size & 0xF) != 0 ||
        firmware_size - firmware_info.size >= 16 ||
        firmware_size > Firmware_Max_Size ||
        calculate_crc(&firmware_info) != firmware_info.crc)
    {
        res = 2;
        UB_LOG("The Firmware is invalid! calculate_crc = 0x[%x]\r\n", calculate_crc(&firmware_info));
        goto firmware_update_over;
    }
    //===============================================校验通过===============================================//
#ifdef UB_SECURE_AES_ENABLE
    memcpy(IV, IV_Init, sizeof(IV)); //重置密文分组
#endif
    //===============================================更新固件===============================================//
    //进行 APP 执行区域的扇区擦除
    for (offset = 0; offset < firmware_size; offset += Flash_Alignment)
    {
        res = FLASH_Erase(Firmware_Addr + offset);
        if (res != FLASH_RES_OK)
        {
            UB_LOG("The Firmware [FLASH_Erase] is [%d]!\r\n", res);
            goto firmware_update_over;
        }
    }
    //读出 APP 新固件并写入 APP 执行区域<Chip-Flash>
    for (offset = 0; offset < firmware_size; offset += read_len)
    {
        res = f_read(&file, Read_Buff, sizeof(Read_Buff), &read_len);
        if (FR_OK != res || read_len == 0) //读取文件失败
        {
            UB_LOG("The Firmware [f_read] result = [%d]!\r\n", res);
            goto firmware_update_over;
        }

        if (data_dec != NULL) //解密
        {
            if (data_dec(Read_Buff, Write_Buff, read_len) != 0)
            {
                res = 3;
                UB_LOG("The Firmware [data_dec] result = [%d]!\r\n", res);
                goto firmware_update_over;
            }
        }
        else
        {
            memcpy(Write_Buff, Read_Buff, read_len);
        }

        res = FLASH_Write(Firmware_Addr + offset, (uint32_t *)Write_Buff, read_len >> 2);
        if (res != FLASH_RES_OK)
        {
            UB_LOG("The Firmware [FLASH_Write] is [%d]!\r\n", res);
            goto firmware_update_over;
        }

        UB_LOG("=============== [Firmware] Update [%d]/[%d] Bytes ===============\r\n", offset + read_len, firmware_size);
    }
    //只有完成固件写入才会将信息头写入 Chip-Flash
    dev_info_t dev_info;
    dev_info.boot = Device_Info->boot;
    dev_info.boot_version = Device_Info->boot_version;
    dev_info.app = firmware_info; //只改变了 app 固件头, 其他都是回读重写
    dev_info.reserve[0] = Device_Info->reserve[0];
    dev_info.reserve[1] = Device_Info->reserve[1];
    //写入设备配置信息
    res = set_device_info(&dev_info);
    if (res != 0)
    {
        UB_LOG("The [set_device_info] result is [%d]!\r\n", res);
        goto firmware_update_over;
    }
    //===============================================更新结束===============================================//
firmware_update_over:
    f_close(&file);
    if (res == 0) //固件更新成功, 则将本地存储中的待更新固件删掉, 避免后续重复更新
    {
        /*res = */ f_unlink(path);
        //TODO 重命名
    }
    return res;
}
